package mc.support.web;

import mc.support.util.PlainUtil;
import mc.support.web.action.ActionContainer;
import mc.support.web.action.ActionInvokerRouter;
import mc.support.web.action.ActionWrapper;
import mc.support.web.action.WriteException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created with IntelliJ IDEA. <br/>
 * User: francis    <br/>
 * Date: 13-11-5    <br/>
 * Time: 14:49  <br/>
 */

public class RouterServlet extends HttpServlet implements WebConstants {

    private static Logger log = LoggerFactory.getLogger( RouterServlet.class );

    private Map<String, Object> actionCache = new ConcurrentHashMap<String, Object>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doAny( req, resp );
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        doAny( req, resp );
    }

    private void doAny(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        ActionWrapper action = parseRequest( req );

        if ( null != action ) {
            Map<String, Object> params = parseParams( req );
            params.put(REQ_METHOD, action.getMethodName());
            params.put(HTTP_REQUEST, req);
            params.put(HTTP_RESPONSE, resp);
            invokeAction(action, params, resp);
        } else {
            log.warn( "action not found for request: " + req.getPathInfo());
            responseInvalid( resp.getWriter(), "invalid request!" );
        }

    }

    private ActionWrapper parseRequest(HttpServletRequest req ) {
        String[] terms = parseRequestPath( req.getPathInfo() );

        Object action = null;
        String methodName = null;
        if ( null != terms && terms.length >= 2 ) {
            String rawActionName = terms[0];
            methodName = terms[1];
            action = getActionByName( rawActionName );
        }

        return null != action ? new ActionWrapper( action.getClass().getName(),
                methodName, action ) : null;
    }

    private Map<String, Object> parseParams(final HttpServletRequest req) {
        final HttpSession session = req.getSession();
        final Map httpParamMap = req.getParameterMap();
        Map<String, Object> newParams = new HashMap<String, Object>() {
            private static final long serialVersionUID = -3256102304612867807L;
            @Override
            public Object get(Object key) {
                Object value = getFromHttpParamMap( httpParamMap, key );
                value = null != value? value : super.get( key );
                value = null != value ? value : req.getHeader( key.toString() );
                return null != value ? value : session.getAttribute( key.toString() );
            }
        };
        newParams.put( WebConstants.REQ_PARAMS_MAP, httpParamMap );
//        Map<String, Object> reqParams = req.getParameterMap();
//
//        if ( null != reqParams && !reqParams.isEmpty() ) {
//            newParams.put( WebConstants.REQ_PARAMS_MAP, reqParams);
//            for ( Map.Entry<String, Object> e : reqParams.entrySet() ) {
//                String k = e.getKey();
//                Object v = e.getValue();
//                if ( v instanceof String[] ) {
//                    String[] values = (String[]) v;
//                    if ( values.length > 0) {
//                        newParams.put( k, values[0] );
//                    }
//                } else {
//                    newParams.put( k, v );
//                }
//            }
//        }

        return newParams;
    }

    private Object getFromHttpParamMap(Map m, Object key) {
        Object v = m.get( key );
        if ( null != v && v instanceof String[] ) {
            String[] values = (String[]) v;
            if ( values.length > 0) {
                v = values[0];
            }
        }
        return v;
    }


    private Object getActionByName(String name) {
        Object action = actionCache.get( name );
        if ( null == action ) {
            synchronized ( actionCache ) {
                action = actionCache.get( name );
                if ( null == action ) {
                    ActionContainer container = getActionContainer();
                    action = container.getAction( name );
                    if ( null != action ) {
                        actionCache.put( name, action );
                    }
                }
            }
        }

        return action;
    }

    private ActionContainer getActionContainer() {
        return (ActionContainer) getServletContext()
                        .getAttribute(WebConstants.ACTION_CONTAINER);
    }

    private String[] parseRequestPath(String path) {
        String[] terms = path.split("/");
        if ( terms.length >= 2 ) {
            String relative = terms[1];
            terms = relative.split("\\.");
        } else {
            terms = null;
        }

        return terms;
    }

    private void invokeAction(ActionWrapper action, Map<String, Object> params, HttpServletResponse resp ) {
        try {
            ActionInvokerRouter.getInstance().invoke( action, params );
        } catch (Exception e) {

            PrintWriter printWriter = null;
            if ( e instanceof WriteException ) {
                WriteException we = (WriteException) e;
                printWriter = we.getPrintWriter();
                e = we.getOriginalException();
            } else {
                try {
                    printWriter = resp.getWriter();
                } catch (IOException ex) {
                    log.error( "Write Failure Response Fail: {} ", PlainUtil.getStackTrace( ex ) );
                }
            }

            String msg = PlainUtil.getStackTrace( e );
            String prefix = "Global Web Layer Exception:";
            log.error( prefix + " {} ", msg );

            responseInvalid( printWriter, prefix + msg );
        }
    }

    private void responseInvalid(PrintWriter printWriter, String msg) {
        printWriter.print( msg );
        printWriter.flush();
    }

}
